namespace WpfWatermarkTextBox.Behaviors
{
	#region #using Directives

	using System;
	using System.ComponentModel;
	using System.Windows;
	using System.Windows.Controls;

	#endregion

	public sealed class WatermarkTextBoxBehavior
	{
		private readonly TextBox m_TextBox;
		private TextBlockAdorner m_TextBlockAdorner;

		private WatermarkTextBoxBehavior(TextBox textBox)
		{
			if (textBox == null)
			{
				throw new ArgumentNullException("textBox");
			}

			this.m_TextBox = textBox;
		}

		#region Behavior Internals

		public static readonly DependencyProperty EnableWatermarkProperty = DependencyProperty.RegisterAttached("EnableWatermark", typeof(bool),
																												typeof(WatermarkTextBoxBehavior),
																												new UIPropertyMetadata(false,
																																	   OnEnableWatermarkChanged));

		private static readonly DependencyProperty WatermarkTextBoxBehaviorProperty = DependencyProperty.RegisterAttached("WatermarkTextBoxBehavior",
																														  typeof(WatermarkTextBoxBehavior),
																														  typeof(WatermarkTextBoxBehavior),
																														  new UIPropertyMetadata(null));

		private static WatermarkTextBoxBehavior GetWatermarkTextBoxBehavior(DependencyObject obj)
		{
			return (WatermarkTextBoxBehavior)obj.GetValue(WatermarkTextBoxBehaviorProperty);
		}

		private static void SetWatermarkTextBoxBehavior(DependencyObject obj, WatermarkTextBoxBehavior value)
		{
			obj.SetValue(WatermarkTextBoxBehaviorProperty, value);
		}

		public static bool GetEnableWatermark(TextBox obj)
		{
			return (bool)obj.GetValue(EnableWatermarkProperty);
		}

		public static void SetEnableWatermark(TextBox obj, bool value)
		{
			obj.SetValue(EnableWatermarkProperty, value);
		}

		private static void OnEnableWatermarkChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			if (e.OldValue != null)
			{
				bool enabled = (bool)e.OldValue;

				if (enabled)
				{
					var textBox = (TextBox)d;
					var behavior = GetWatermarkTextBoxBehavior(textBox);
					behavior.Detach();

					SetWatermarkTextBoxBehavior(textBox, null);
				}
			}

			if (e.NewValue != null)
			{
				bool enabled = (bool)e.NewValue;

				if (enabled)
				{
					var textBox = (TextBox)d;
					var behavior = new WatermarkTextBoxBehavior(textBox);
					behavior.Attach();

					SetWatermarkTextBoxBehavior(textBox, behavior);
				}
			}
		}

		private void Attach()
		{
			this.m_TextBox.Loaded += TextBoxLoaded;
			this.m_TextBox.TextChanged += TextBoxTextChanged;
			this.m_TextBox.DragEnter += TextBoxDragEnter;
			this.m_TextBox.DragLeave += TextBoxDragLeave;
		}

		private void Detach()
		{
			this.m_TextBox.Loaded -= TextBoxLoaded;
			this.m_TextBox.TextChanged -= TextBoxTextChanged;
			this.m_TextBox.DragEnter -= TextBoxDragEnter;
			this.m_TextBox.DragLeave -= TextBoxDragLeave;
		}

		private void TextBoxDragLeave(object sender, DragEventArgs e)
		{
			UpdateAdorner();
		}

		private void TextBoxDragEnter(object sender, DragEventArgs e)
		{
			this.m_TextBox.TryRemoveAdorners<TextBlockAdorner>();
		}

		private void TextBoxTextChanged(object sender, TextChangedEventArgs e)
		{
			bool hasText = !string.IsNullOrEmpty(this.m_TextBox.Text);
			SetHasText(this.m_TextBox, hasText);
		}

		private void TextBoxLoaded(object sender, RoutedEventArgs e)
		{
			Init();
		}

		#endregion

		#region Attached Properties

		private static readonly DependencyPropertyKey HasTextPropertyKey = DependencyProperty.RegisterAttachedReadOnly("HasText", typeof(bool),
																													   typeof(WatermarkTextBoxBehavior),
																													   new UIPropertyMetadata(false));

		public static readonly DependencyProperty HasTextProperty = HasTextPropertyKey.DependencyProperty;

		public static readonly DependencyProperty LabelProperty = DependencyProperty.RegisterAttached("Label", typeof(string),
																									  typeof(WatermarkTextBoxBehavior));

		public static readonly DependencyProperty LabelStyleProperty = DependencyProperty.RegisterAttached("LabelStyle", typeof(Style),
																										   typeof(WatermarkTextBoxBehavior));

		public static string GetLabel(TextBox obj)
		{
			return (string)obj.GetValue(LabelProperty);
		}

		public static void SetLabel(TextBox obj, string value)
		{
			obj.SetValue(LabelProperty, value);
		}

		public static Style GetLabelStyle(TextBox obj)
		{
			return (Style)obj.GetValue(LabelStyleProperty);
		}

		public static void SetLabelStyle(TextBox obj, Style value)
		{
			obj.SetValue(LabelStyleProperty, value);
		}

		public static bool GetHasText(TextBox obj)
		{
			return (bool)obj.GetValue(HasTextProperty);
		}

		private static void SetHasText(TextBox obj, bool value)
		{
			obj.SetValue(HasTextPropertyKey, value);
		}

		#endregion

		private void Init()
		{
			this.m_TextBlockAdorner = new TextBlockAdorner(this.m_TextBox, GetLabel(this.m_TextBox), GetLabelStyle(this.m_TextBox));
			UpdateAdorner();

			var focusProp = DependencyPropertyDescriptor.FromProperty(UIElement.IsFocusedProperty, typeof(FrameworkElement));
			if (focusProp != null)
			{
				focusProp.AddValueChanged(this.m_TextBox, (sender, args) => UpdateAdorner());
			}

			var containsTextProp = DependencyPropertyDescriptor.FromProperty(HasTextProperty, typeof(TextBox));
			if (containsTextProp != null)
			{
				containsTextProp.AddValueChanged(this.m_TextBox, (sender, args) => UpdateAdorner());
			}
		}

		private void UpdateAdorner()
		{
			if (GetHasText(this.m_TextBox) || this.m_TextBox.IsFocused)
			{
				// Hide the Watermark Label if the adorner layer is visible
				this.m_TextBox.ToolTip = GetLabel(this.m_TextBox);
				this.m_TextBox.TryRemoveAdorners<TextBlockAdorner>();
			}
			else
			{
				// Show the Watermark Label if the adorner layer is visible
				this.m_TextBox.ToolTip = null;
				this.m_TextBox.TryAddAdorner<TextBlockAdorner>(this.m_TextBlockAdorner);
			}
		}
	}
}
